//
// Created by wxd on 2025/7/28.
// 轨迹球相机控制器
//

/**
 * 分析轨迹球控制器算法
 * 常用的旋转算法：欧拉角，四元数，下面我们分别采用欧拉角和四元数来实现轨迹球相机控制器算法
 *
 * 欧拉角算法
 *    所谓的欧拉角旋转，实际上就是将旋转正交分解到{x,y,z}三个轴的旋转，然后在进行合成，排列组合可知，一共有6中合成顺序
 *    注意OpenGL是右手系，右侧: +x, 上侧: +y, 屏幕外: +z (不同的图形框架坐标系的不同会导致欧拉角对应的旋转轴存在偏差)
 *    Euler-Angle: {pitch, yaw, roll} -> {x, y, z} -> {俯仰角，偏航角，滚转角}
 *    Euler-Angle旋转顺序：
 *      pitch：仰+(绕着x轴逆时)，俯-(绕着x轴顺时)；
 *      yaw: 右+(绕着y轴顺时), 左-(绕着y轴逆时)；
 *      roll: 向右滚（顺时针）为正，向左滚（逆时针）为负
 *
 * 基于欧拉角实现的相机控制器算法
 *    我们先简化模型：将相机放置到一个半径为R的，球心在坐标原点的球面上面移动，用户的输入操作，比如：点击模型之后左/右滑动鼠标，上下滑动鼠标
 *    左滑动鼠标 -> 绕着y轴顺时针；右滑动鼠标 -> 绕着y轴逆时针 -> up不变，pos变化，right变化
 *    上滑动鼠标 -> 绕着x轴逆时针；下滑动鼠标 —> 绕着x轴顺时针 -> up变化，pos变化，right变化
 *    目前针对轨迹球算法，暂时用不到roll的信息，因为不存在相机的自旋转
 *
 *    约定相关符号：
 *      s：鼠标灵敏度
 *      cx：上一次鼠标的x位置，cy：上一次鼠标y位置
 *      x：当前鼠标x位置，y：当前鼠标y位置
 *      dx = x - cx, dy = y - cy
 *    我们的核心算法就是找到dx，dy的变换与pitch和yaw角度之间的关系，从而更新当前相机的位置，针对我们自定义的相机系统，
 *    Camera的核心参数：{pos, up, right}, 最终我们需要通过上述的关系计算出pos，up，right，然后更新相机矩阵
 *
 * 四元数算法
 */

#pragma once

#include "BaseCameraControl.h"

class TrackBallCameraControl : public BaseCameraControl {
public:
    TrackBallCameraControl() :
            m_MouseSensitivity(0.f), m_TrackBallRadius(0.f), m_TrackBallAutoRotateSpeed(0.f), m_isSyntropy(false) {}

    TrackBallCameraControl(const float sensitivity, const float radius) :
            m_MouseSensitivity(sensitivity), m_TrackBallRadius(radius), m_TrackBallAutoRotateSpeed(0.f), m_isSyntropy(false) {}

    TrackBallCameraControl(const float sensitivity, const float radius, const float speed) :
            m_MouseSensitivity(sensitivity), m_TrackBallRadius(radius), m_TrackBallAutoRotateSpeed(speed), m_isSyntropy(false) {}

    TrackBallCameraControl(const float sensitivity, const float radius, const float speed, const bool isSyntropy) :
            m_MouseSensitivity(sensitivity), m_TrackBallRadius(radius), m_TrackBallAutoRotateSpeed(speed), m_isSyntropy(isSyntropy) {}

    void setMouseSensitivity(float sensitivity);
    void setTrackBallRadius(float radius);
    void setTrackBallAutoRotateSpeed(float speed);
    void setSyntropy(bool isSyntropy);
    void setEnableRotateCenterSelf(bool isSyntropy);
    void setLookAtCenter(const glm::vec3& lookAtCenter);

    void onCursor(float xpos, float ypos) override;                             // 处理模型的轨迹球运动(实际上修改的是相机的运动，根据相对运动，让模型看上去在轨迹球上面运动)
    void onScroll(double yoffset) override;                                     // 处理模型的放缩

    void setEnableEulerAngleYaw(bool enable);
    void setEnableEulerAnglePitch(bool enable);

private:
    float m_MouseSensitivity;                                                  // 鼠标沿轨迹球移动的灵敏度
    float m_TrackBallRadius;                                                   // 轨迹球半径
    float m_TrackBallAutoRotateSpeed;                                          // 相机沿轨迹球自移动的速度

    bool m_isSyntropy;                                                         // 模型运动是否和相机保持一致
    bool m_EnableRotateCenterSelf = false;                                     // 是否绕着模型中心进行轨迹球旋转(这里更加适合场景，暂时没有实现所谓的渲染场景类，绕着场景中心进行轨迹旋转)
    glm::vec3 m_LookAtCenter = glm::vec3(0.f);                           // 轨迹球相机观察轨迹旋转的中心

    bool m_EnableEulerAngleYaw = true;                                         // 是否允许yaw角转动
    bool m_EnableEulerAnglePitch = true;                                       // 是否允许pitch角转动

    void onRotateAlongTrackBall(float angleX, float angleY);

    bool isUnMove() const;
    void pitchUpdate(float angle);
    void yawUpdate(float angle);
};
